DocGenerator.js ➔ ???   B
last analyzed

Complexity

Conditions 2
Paths 64

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 18
Bugs 0 Features 18
Metric Value
c 18
b 0
f 18
nc 64
dl 0
loc 46
rs 8.9411
cc 2
nop 3
1
const _             = require('lodash'),
2
      fs            = require('fs'),
3
      Handlebars    = require('handlebars'),
4
      HTML          = 'HTML',
5
      path          = require('path'),
6
      BaseGenerator = require('./BaseGenerator'),
7
      SwaggerParser = require('./SwaggerParser'),
8
      Markdown      = 'Markdown'
9
10
/**
11
 * @class DocGenerator
12
 * Documentation generator (html or MarkDown)
13
 *
14
 * @property {String} type Output documentation type
15
 * @property {String} version Swagger version
16
 * @property {String} destination Output destination
17
 * @property {String} templatePath Path to template
18
 * @property {ParserOptions} parserOptions Swagger parser options
19
 * @property {Function} generatorCallback Custom generator callback
20
 * @property {Object.<Object>} additionalLayouts Additional layouts for Handlebars in format <code><file-name-or-relative-path>: <template-name></code>
21
 * @property {Object.<Object>} additionalHelpers Additional helpers for Handlebars in format <code><helper-name>: function (Handlebars) {
22
 *  // Some helper detail
23
 * }</code>
24
 * @property {String} moduleName Module name
25
 * @property {String} className Class name
26
 * @property {String} destination Output doc destination
27
 * @property {String} docsPath Method definitions path
28
 * @property {String} modelPath Model path
29
 */
30
class DocGenerator extends BaseGenerator {
31
32
  /**
33
   * DocGenerator constructor
34
   *
35
   * @param {String} type Documentation type (html or Markdown). One of (Markdown or HTML)
36
   * @param {DocGeneratorOptionsObject} options Swagger documentation generator options
37
   * @param {String} version Swagger version (default is 2.0)
38
   */
39
  constructor (type, options, version) {
40
    super()
41
42
    version                = version || '2.0'
43
    options                = options || {}
44
    this.type              = type
45
    this.version           = version
46
    this.generatorCallback = options.generatorCallback
47
    this.templatePath      = options.templatePath || (path.join(__dirname, this.version, 'templates', this.type.toLowerCase()))
48
    this.data              = options.swagger
49
    this.additionalLayouts = options.additionalLayouts || {}
50
    this.parserOptions     = options.parserOptions
51
    this.destination       = options.destination || path.join(__dirname, '/../dist')
52
    this._checkFile(false, 'destination', 'Destination folder not found')
53
    this.additionalHelpers = options.additionalHelpers || {}
54
    this.filename          = options.inputJson
55
    this.moduleName        = options.moduleName
56
    this.className         = options.className
57
    this.modelPath         = options.modelPath
58
    this.docsPath          = options.docsPath
59
60
    this._checkFile()
61
    this._content = fs.readFileSync(this.filename).toString()
62
63
    this.data = this._parseData()
64
65
    this._checkFile(false, 'templatePath', 'Template path does not exists')
66
67
    let parser                  = new SwaggerParser(this.version, this.data, this.parserOptions || {
68
      className     : this.className,
69
      moduleName    : this.moduleName,
70
      packageName   : options.packageName,
71
      packageVersion: options.packageVersion,
72
      modelPath     : this.modelPath,
73
      docsPath      : this.docsPath
74
    })
75
    this.swaggerData            = parser.parse()
76
    this.swaggerData.moduleName = this.moduleName
77
    this.swaggerData.className  = this.className
78
79
    // fs.writeFileSync(path.join(process.cwd(), 'api-data.json'), JSON.stringify(this.swaggerData))
80
81
    if (!_.size(this.data)) {
82
      throw new Error('Swagger data is empty')
83
    }
84
  }
85
86
  /**
87
   * @const {String} HTML
88
   *
89
   * @return {string}
90
   */
91
  static get HTML () {
92
    return HTML
93
  }
94
95
  /**
96
   * @const {String} Markdown
97
   *
98
   * @return {string}
99
   */
100
  static get Markdown () {
101
    return Markdown
102
  }
103
104
  /**
105
   * Generate documentation
106
   */
107
  generate () {
108
    if (typeof this.generatorCallback === 'function') {
109
      return this.generatorCallback.apply(this, [this.data])
110
    }
111
112
    this._prepareHandlebars()
113
114
    this._generateMain()
115
116
    this._generateMethods()
117
    this._generateModels()
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
118
  }
119
120
  _generateMain () {
121
    let template = fs.readFileSync(path.join(this.templatePath, 'md.hbs')).toString(),
122
        content  = Handlebars.compile(template)(this.swaggerData)
123
124
    this._writeContent(content, this._getMainFileName())
125
  }
126
127
  _getMainFileName () {
128
    return this.type === DocGenerator.HTML ? 'index.html' : 'README.MD'
129
  }
130
131
  _getExt () {
132
    return this.type === DocGenerator.HTML ? 'html' : 'md'
133
  }
134
135
  _writeContent (content, fileName) {
136
    fs.writeFileSync(path.join(this.destination, fileName), content)
137
  }
138
139
  _generateModels () {
140
    let _self = this,
141
        template = fs.readFileSync(path.join(this.templatePath, 'model.hbs')).toString(),
142
        content = ''
143
144
    _.each(this.swaggerData.definitions, (config, name) => {
145
      config.modelName = name
146
      content = Handlebars.compile(template)(config)
147
      _self._writeContent(content, path.join(this.modelPath, name + '.' + _self._getExt()))
148
    })
149
  }
150
151
  _generateMethods () {
152
    let template = fs.readFileSync(path.join(this.templatePath, 'method.hbs')).toString(),
153
        responseTemplate = fs.readFileSync(path.join(this.templatePath, 'response.hbs')).toString(),
154
        content  = '',
155
        _self    = this
156
    Handlebars.registerPartial('response', responseTemplate)
157
    _.each(this.swaggerData.methods, (config, index) => {
0 ignored issues
show
Unused Code introduced by
The parameter index is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
158
      content = Handlebars.compile(template)(config)
159
      _self._writeContent(content, path.join(_self.docsPath, config.methodName + '.' + _self._getExt()))
160
    })
161
  }
162
163
  _prepareHandlebars () {
164
    this._registerPartial('methodList')
165
    this._registerPartial('securityDefinitions')
166
167
    let _self = this
168
169
    _.each(this.additionalLayouts, (file, name) => {
170
      _self._registerPartial(file, name)
171
    })
172
173
    this._registerHelpers()
174
  }
175
176
  _registerPartial (filename, templateName) {
177
    templateName = templateName || filename
178
    if (_.isNumber(templateName)) {
179
      templateName = filename.split('/')
180
181
      let clearTemplate = ''
182
183
      for (let i = 0; i < templateName.length; i++) {
184
        if ((clearTemplate = _.trim(templateName[i])).length) {
185
          templateName = clearTemplate
186
        }
187
      }
188
    }
189
    let fileContent = fs.readFileSync(path.join(this.templatePath, filename + '.hbs')).toString()
190
191
    Handlebars.registerPartial(templateName, fileContent)
192
  }
193
194
  _registerHelpers () {
195
196
    Handlebars.registerHelper('ifCond', function (v1, v2, options) {
197
      if (v1 === v2) {
198
        return options.fn(this)
199
      }
200
      return options.inverse(this)
201
    })
202
203
    Handlebars.registerHelper('ifHas', function (object, paramName, value, options) {
204
      for (let i in object) {
205
        if (!object.hasOwnProperty(i)) {
206
          continue
207
        }
208
209
        let has = object[i][paramName] === value
210
211
        if (has) {
212
          return options.fn(this)
213
        }
214
215
        return options.inverse(this)
216
      }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
217
    })
218
219
    Handlebars.registerHelper('breaklines', function(text) {
220
      text = Handlebars.Utils.escapeExpression(text);
221
      text = text.replace(/(\r\n|\n|\r)/gm, '<br>');
222
      return new Handlebars.SafeString(text);
223
    });
224
225
    Handlebars.registerHelper('requireFilter', function (require) {
226
      return require ? 'required' : 'optional'
227
    })
228
229
    Handlebars.registerHelper('ifDefine', function (object, paramName, options) {
230
      if (typeof object[paramName] !== 'undefined') {
231
        return options.fn(this)
232
      }
233
234
      return options.inverse(this)
235
    })
236
237
    Handlebars.registerHelper('ifLength', function (object, options) {
238
      if (typeof object === 'undefined') {
239
        return options.inverse(this)
240
      }
241
      return object.length ? options.fn(this) : options.inverse(this)
242
    })
243
244
    _.each(this.additionalHelpers, (cb, name) => {
245
      if (typeof cb === 'function') {
246
        cb(name, Handlebars)
247
      }
248
    })
249
  }
250
}
251
252
/**
253
 * @ignore module
254
 * @ignore module.exports
255
 */
256
module.exports = DocGenerator